﻿using System;
using System.Text;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;


namespace Campus
{
    class MouseMenu
    {
        #region Fields and Properties

        private List<MenuGraphic> m_graphicList;
        private List<AnimatedMenuGraphic> m_animatedGraphicList;
        private Dictionary<string, MenuButton> m_buttonDict;

        private MouseState m_oldMouseState;
        private MenuButton m_buttonPressed;

        private Keys? m_toggleKey;
        private KeyboardState m_oldKeyboardState;

        /// <summary>
        /// The texture (graphic) for the MouseMenu object.
        /// </summary>
        public Texture2D Texture { get; private set; }

        /// <summary>
        /// The position, in screen coordinates, of the MouseMenu object.
        /// </summary>
        public Vector2 Position { get; set; }

        /// <summary>
        /// Whether the MouseMenu object is visible or not.
        /// </summary>
        public bool Visible { get; set; }

        /// <summary>
        /// If a button is in the 'selected' state, any white areas will change to this color.
        /// </summary>
        public Color SelectedColor { get; set; }

        #endregion

        #region Constructors

        /// <summary>
        /// The 'main' constructor, all other constructors call this one.
        /// </summary>
        /// <param name="texture">The texture of the MouseMenu</param>
        /// <param name="position">The position, in screen coordinates, of the MouseMenu</param>
        /// <param name="graphics">The list of MenuGraphics to be used by the MouseMenu</param>
        /// <param name="buttons">The list of MenuButtons to be used by the MouseMenu</param>
        /// <param name="toggle">The key, when pressed, which toggles the visibility of the MouseMenu</param>
        public MouseMenu(Texture2D texture, Vector2 position, List<MenuGraphic> graphics, List<AnimatedMenuGraphic> animatedGraphics, List<MenuButton> buttons, Keys? toggle)
        {
            this.Texture = texture;
            this.Position = position;
            this.SelectedColor = Color.White;

            this.m_graphicList = graphics;
            this.m_animatedGraphicList = animatedGraphics;

            // Fill the dictionary with the list passed in
            this.m_buttonDict = new Dictionary<string, MenuButton>();

            foreach (MenuButton button in buttons)
            {
                this.m_buttonDict.Add(button.Name, button);
            }

            this.m_oldMouseState = Mouse.GetState();
            this.m_buttonPressed = null;
            this.m_toggleKey = toggle;
            this.m_oldKeyboardState = Keyboard.GetState();
        }

        public MouseMenu(Texture2D texture, Vector2 position, Keys? toggle) : 
            this(texture, position, new List<MenuGraphic>(), new List<AnimatedMenuGraphic>(), new List<MenuButton>(), toggle) {}

        public MouseMenu(Texture2D texture, Vector2 position, List<MenuGraphic> graphics, Keys? toggle) :
            this(texture, position, graphics, new List<AnimatedMenuGraphic>(), new List<MenuButton>(), toggle) { }

        public MouseMenu(Texture2D texture, Vector2 position, List<MenuButton> buttons, Keys? toggle) :
            this(texture, position, new List<MenuGraphic>(), new List<AnimatedMenuGraphic>(), buttons, toggle) { }

        #endregion

        #region Methods

        /// <summary>
        /// Toggles the visibility of the MouseMenu object.
        /// </summary>
        public void ToggleVisibility()
        {
            Visible = !Visible;
        }

        /// <summary>
        /// Retrieves the current state of the button with the name supplied as the
        /// parameter.  Returns the invalid state if the supplied name does not match
        /// a button contained in the MouseMenu
        /// </summary>
        /// <param name="buttonName">The name of the button.</param>
        /// <returns>The state of the button, invalid if no match.</returns>
        public MenuButtonState GetButtonState(string buttonName)
        {
            MenuButton button = m_buttonDict[buttonName];

            if (button != null)
            {
                return button.State;
            }

            return MenuButtonState.invalid;
        }

        /// <summary>
        /// Sets the state for the button with the name supplied as the parameter.  Returns
        /// void so be careful with the spelling of the button's name.
        /// </summary>
        /// <param name="buttonName">The name of the button.</param>
        /// <param name="state">The new state of the button.</param>
        public void SetButtonState(string buttonName, MenuButtonState state)
        {
            MenuButton button = m_buttonDict[buttonName];

            if (button != null)
            {
                button.State = state;
            }
        }

        /// <summary>
        /// Sets the button with the button name 'buttonName' as the selected option in the MouseMenu.
        /// </summary>
        /// <param name="buttonName">The name of the button to set as selected.</param>
        public void SetButtonAsSelected(string buttonName)
        {
            foreach (MenuButton button in m_buttonDict.Values)
            {
                button.Selected = false;
            }

            MenuButton selectedButton = m_buttonDict[buttonName];

            if (selectedButton != null)
            {
                selectedButton.Selected = true;
            }
        }

        /// <summary>
        /// The update function of the MouseMenu object.  Checks whether the
        /// MouseMenu is visible and, if it is, checks whether the mouse is
        /// in a position to trigger a rollover, click, etc. 'event' on one of
        /// the MenuButtons it owns.  It also toggles the visibility of the
        /// MouseMenu if the specified (at construction time) toggle key has
        /// been pressed.
        /// </summary>
        public void Update(GameTime gameTime)
        {

            KeyboardState newKeyboardState = Keyboard.GetState();

            // If the toggle key has been pressed, toggle the visibility of the MouseMenu
            if (m_toggleKey != null)
            {
                if (m_oldKeyboardState.IsKeyUp(m_toggleKey.Value) && newKeyboardState.IsKeyDown(m_toggleKey.Value))
                {
                    ToggleVisibility();
                }
            }

            if (Visible)
            {
                if (m_animatedGraphicList != null)
                {
                    foreach (AnimatedMenuGraphic amg in m_animatedGraphicList)
                    {
                        amg.Update(gameTime);
                    }
                }

                // Get the new state of the mouse
                MouseState newMouseState = Mouse.GetState();

                Vector2 mousePos = new Vector2(newMouseState.X, newMouseState.Y);

                // Cycle through all the buttons owned by this MouseMenu object
                foreach (MenuButton button in m_buttonDict.Values)
                {
                    // Ignore interacting with the button if it is in the invisible
                    // or invalid states
                    if (button.State != MenuButtonState.invisible &&
                        button.State != MenuButtonState.invalid)
                    {

                        // If the mouse is within the border of the button
                        if (MouseIsInsideMenuButton(mousePos, button))
                        {
                            if (newMouseState.LeftButton == ButtonState.Pressed)
                            {
                                // The mouse button has just been 'clicked'
                                if (m_oldMouseState.LeftButton == ButtonState.Released)
                                {
                                    m_buttonPressed = button;
                                }
                            }
                            else
                            {
                                // The mouse button has been released
                                if (m_oldMouseState.LeftButton == ButtonState.Pressed)
                                {
                                    // If the button that the mouse just 'released' over is the same
                                    // one that it was on when it 'clicked'
                                    if (m_buttonPressed == button)
                                    {
                                        button.InvokeButton();
                                    }
                                }
                            }
                            // If the button has been pressed and the cursor currently lies within it
                            if (button == m_buttonPressed)
                            {
                                button.State = MenuButtonState.active;
                            }
                            // If no button has been clicked on then show the button's rollover texture
                            else if (m_buttonPressed == null)
                            {
                                button.State = MenuButtonState.rollover;
                            }
                        }
                        // If the mouse is NOT within the button's border
                        else
                        {
                            button.State = MenuButtonState.idle;
                        }
                    }
                }

                // If the button was held and now released, reset the 'button pressed' field
                if (m_oldMouseState.LeftButton == ButtonState.Pressed &&
                    newMouseState.LeftButton == ButtonState.Released)
                {
                    m_buttonPressed = null;
                }

                // Change the new state to its old state
                m_oldMouseState = newMouseState;
            }
            // Change the new state to its old state
            m_oldKeyboardState = newKeyboardState;
        }

        /// <summary>
        /// The draw function for the MouseMenu and its MenuButtons and MenuGraphics.
        /// </summary>
        /// <param name="spriteBatch">The Spritebatch into which it will draw.</param>
        public void Draw(SpriteBatch spriteBatch)
        {
            if (Visible)
            {
                spriteBatch.Begin();

                // Draw the MouseMenu's background
                if (this.Texture != null)
                {
                    spriteBatch.Draw(this.Texture, this.Position, Color.White);
                }

                // Draw all the buttons
                if (m_buttonDict != null)
                {
                    foreach (MenuButton button in m_buttonDict.Values)
                    {
                        if (button.State != MenuButtonState.invisible && button.Texture != null)
                        {
                            if (!button.Selected)
                            {
                                spriteBatch.Draw(button.Texture, (this.Position + button.Position), Color.White);
                            }
                            else
                            {
                                spriteBatch.Draw(button.Texture, (this.Position + button.Position), SelectedColor);
                            }
                        }
                    }
                }

                // Draw all the graphics
                if (m_graphicList != null)
                {
                    foreach (MenuGraphic graphic in m_graphicList)
                    {
                        spriteBatch.Draw(graphic.Texture, (this.Position + graphic.Position), Color.White);
                    }
                }
                if (m_animatedGraphicList != null)
                {
                    foreach (AnimatedMenuGraphic amg in m_animatedGraphicList)
                    {
                        spriteBatch.Draw(amg.Texture, (this.Position + amg.Position), Color.White);
                    }
                }

                spriteBatch.End();
            }
        }

        private bool MouseIsInsideMenuButton(Vector2 mousePosition, MenuButton button)
        {
            if (button.Texture != null)
            {
                int minX = (int)(this.Position.X + button.Position.X);
                int minY = (int)(this.Position.Y + button.Position.Y);
                int maxX = minX + button.Texture.Width;
                int maxY = minY + button.Texture.Height;

                if (mousePosition.X >= minX && mousePosition.X <= maxX &&
                    mousePosition.Y >= minY && mousePosition.Y <= maxY)
                {
                    return true;
                }
            }

            return false;
        }

        #endregion
    }
}
